package const_prop;

import java.util.HashSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import flow_graph.AssemFlowGraph;
import graph.Node;
import temp.Temp;
import util.List;
import assem.Instr;

public class ConstantPropagation {

	public List<Instr> instrs;
	private ReachingDefs rdefs;
	private AssemFlowGraph cfg;

	public ConstantPropagation(List<Instr> i)
	{
		boolean change;
		instrs = i;
		
		do {
			cfg = new AssemFlowGraph(instrs);
			rdefs = new ReachingDefs(cfg);
			instrs = null;
			change = constantProp();	
		} 
		while (change);
	}

	private boolean constantProp()
	{
		boolean change = false;
		
		/* Percorre todos os nos da arvore e verifica onde pode ser substituido por constantes */
		for(List<Node> nos = cfg.nodes(); nos != null ; nos = nos.tail)
		{
			Node no = nos.head;

			/* Verifica todos os 'temp' que ele usa. Se existir apenas uma e for uma atribuicao de constante,
			 * pode-se substituir esta constante no no 'no'. */
			int temp_i = 0;
			Instr newInstr = cfg.instr(no);
			
			for(List<Temp> temps = cfg.use(no); temps != null; temps = temps.tail, temp_i++)
			{
				Temp temp = temps.head;

				// Recupera todos os nos em que o temp e definido
				HashSet<Instr> def_nos = rdefs.Defs(temp);

				if(def_nos != null)
				{
					// Recupera a lista de definicoes na entrada do no atual
					HashSet<Instr> act_nos = rdefs.In(no, temp);
					
					String cons_value = change_cons(act_nos);
					
					if (cons_value != null)
					{
						//System.out.println(temp_i);
						//System.out.print("OLD: " + cfg.instr(no).assem + " - ");
						
						/* Substitui no no atual o 'temp' pela CONSTANTE */
						/* Verifica se mudou string */
						String str = cfg.instr(no).assem;
						cfg.instr(no).assem = cfg.instr(no).assem.replace(" `s" + temp_i, " DWORD" + cons_value);
						
						if (! str.equals(cfg.instr(no).assem))
						{
							change = true;
						
							Pattern pattern = Pattern.compile("`s\\d");
							Matcher match = pattern.matcher(cfg.instr(no).assem);
							
							while (match.find())
							{
								int temp_num = Integer.parseInt(match.group().split("`s")[1]);
								
								if (temp_num > temp_i)
									temp_num--;
								
								cfg.instr(no).assem = cfg.instr(no).assem.replace(match.group(), "`s"+temp_num);
							}
							
							/* remove o temporario retirado da liste de USE do nó */
							if (cfg.instr(no) instanceof assem.OPER)
							{
								((assem.OPER)cfg.instr(no)).src.remove(temp_i);
								if (((assem.OPER)cfg.instr(no)).src.head == null)
									((assem.OPER)cfg.instr(no)).src = null;
							}
							else if (cfg.instr(no) instanceof assem.MOVE)
							{
								((assem.MOVE)cfg.instr(no)).src.remove(temp_i);
								if (((assem.MOVE)cfg.instr(no)).src.head == null)
									((assem.MOVE)cfg.instr(no)).src = null;
							}
							
							if (cfg.instr(no).jumps() == null)
								newInstr = new assem.OPER(cfg.instr(no).assem, cfg.instr(no).def(), cfg.instr(no).use());
							else
								newInstr = new assem.OPER(cfg.instr(no).assem, cfg.instr(no).def(), cfg.instr(no).use(), cfg.instr(no).jumps().labels);
						}
						
						//System.out.println("NEW: " + cfg.instr(no).assem);
					}
				}
			}

			/* Insere a instrucao na lista de instrucao na ordem inversa */
			instrs = new List<Instr>(newInstr, instrs);
		}
		
		/* Faz a insercao na ordem correta */
		List<Instr> instrs_temp = instrs;
		for (instrs = null ; instrs_temp != null; instrs_temp = instrs_temp.tail)
		{
			instrs = new List<Instr> (instrs_temp.head, instrs);
		}
		
		return change;
	}

	private String change_cons(HashSet<Instr> in)
	{
		if (in.size() == 1)
		{
			Instr instr = in.iterator().next();
			
			/* Existe apenas uma definição, verifica se é constante */
			if (instr.assem.split(" ")[0].equals("mov") && cfg.use(cfg.node(instr)) == null)
			{
				//System.out.println(instr.assem);
        		String instruction = cfg.instr(cfg.node(instr)).assem;
        		return instruction.split(",")[1];
			}
			
			return null;
		}
		else
		{
			return null;
		}
	}

	public ReachingDefs getRdefs()
	{
		return rdefs;
	}

	public AssemFlowGraph getCfg()
	{
		return cfg;
	}
}
